﻿using System.Net;
using System.Numerics;
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using up6.db.biz;
using up6.db.database.down2.sql;
using up6.db.database.up6.sql;
using up6.db.model;
using up6.utils;
using Up6.core.Up6Manager.Biz;
using Up6.core.Up6Manager.Models.Req;

namespace Up6.core.Up6Manager;

public class Up6Action
{
    /// <summary>
    /// 文件初始化
    /// </summary>
    /// <param name="req"></param>
    /// <returns></returns>
    public async Task<IActionResult> CreateAsync(FileInf f)
    {
        //参数为空
        if (
            string.IsNullOrEmpty(f.md5)
            || string.IsNullOrEmpty(f.uid)
            || string.IsNullOrEmpty(f.sizeLoc)
        )
        {
            return new ContentResult { 
                Content= f.callback + "({\"value\":null,\"ret\":false,\"error\":\"参数为空\"})",
                ContentType= "application/json; charset=utf-8",
                StatusCode =200
            };
        }

        if (!ConfigReader.inFileExts(f.pathLoc))
        {
            string m = f.callback + "({\"value\":\"0\",\"ret\":false,\"error\":\"文件类型非法\"})";//返回jsonp格式数据。

            return new ContentResult
            {
                Content = m,
                ContentType = "application/json; charset=utf-8",
                StatusCode = 200
            };
        }

        WebSafe ws = new WebSafe();
        var ret = ws.validToken(f.token, f);
        //token验证失败
        if (!ret)
        {
            string m = f.callback + "({\"value\":\"0\",\"ret\":false,\"error\":\"token error\"})";//返回jsonp格式数据。

            return new ContentResult
            {
                Content = m,
                ContentType = "application/json; charset=utf-8",
                StatusCode = 200
            };
        }

        PathBuilderUuid pb = new PathBuilderUuid();
        f.pathSvr = pb.genFile(f.uid,f );

        //检查相同文件
        var sql = up6.db.database.up6.sql.SqlFile.build();
        var fsvr = sql.exist_file(f.md5);
        if (fsvr != null) {
            f.nameSvr = fsvr.nameSvr;
            f.pathSvr = fsvr.pathSvr;
            f.pathRel = fsvr.pathRel;
            f.perSvr = fsvr.perSvr;
            f.lenSvr = fsvr.lenSvr;
            f.complete  = fsvr.complete;
            f.lenLocSec = fsvr.lenLocSec;
            f.encryptAgo = fsvr.encryptAgo;
            f.encrypt = fsvr.encrypt;
            f.blockSize = fsvr.blockSize;
            f.blockSizeSec = fsvr.blockSizeSec;
            f.object_key = fsvr.object_key;
            sql.Add(f);
        }//数据库不存在相同文件
        else {
            //创建文件
            var fw = ConfigReader.blockWriter();
            try
            {
                f.object_id = await fw.makeAsync(f);
                f.object_key = f.getObjectKey();
            }
            catch (IOException ie)
            {
                return new ContentResult
                {
                    Content = f.callback+string.Format("(\"value\":\"\",\"error\":\"{0}\",\"ret\":false)",ie.Message),
                    ContentType = "application/json; charset=utf-8",
                    StatusCode = 200
                };
            }

            sql.Add(f);
        }

        //将路径转换成相对路径
        f.pathSvr = pb.absToRel(f.pathSvr);

        string jv = JsonConvert.SerializeObject(f);
        jv = HttpUtility.UrlEncode(jv);
        jv = jv.Replace("+", "%20");
        string json = f.callback + "({\"value\":\"" + jv + "\",\"ret\":true})";//返回jsonp格式数据。

        return new ContentResult
        {
            Content = json,
            ContentType = "application/json; charset=utf-8",
            StatusCode = 200
        };
    }

    public async Task<IActionResult> postAsync(FileInf f,IFormFile block)
    {
        //文件块为空
        if(block==null)
        {
            var j = new
            {
                msg = "文件块数据为空",
                code = "blockDataEmpty",
                offset = -1,
                md5 = "",
                param = new { action = "block" }
            };
            return new JsonResult(j);
        }

        try {
            var stm = block.OpenReadStream();
            //验证块MD5
            this.checkBlockMd5(f.blockMd5, stm);
            //解压
            stm = this.unCompress(f, stm);
            //解密
            stm = this.decryptBlock(stm, f);            
            //验证块大小
            this.validBlockSize(stm, f);
            //验证token
            this.checkToken(f);

            //保存文件块

            PathBuilder pb = new PathBuilder();
            f.pathSvr = pb.relToAbs(f.pathSvr);

            var fw = ConfigReader.blockWriter();
            bool needGenId = !string.IsNullOrEmpty(f.pid);
            if (needGenId) needGenId = 1 == f.blockIndex;
            if (needGenId &&
                (fw.storage == StorageType.FastDFS ||
                fw.storage == StorageType.Minio ||
                fw.storage == StorageType.OSS ||
                fw.storage == StorageType.OBS))
                needGenId = string.IsNullOrEmpty(f.object_id);

            try
            {
                //第一块->创建或生成对象ID
                if (needGenId)
                {
                    f.object_id = await fw.makeAsync(f);
                    f.object_key = f.getObjectKey();
                }

                //子文件=>保存到层级信息文件
                if (!string.IsNullOrEmpty(f.pidRoot)) f.saveScheme();

                //写入块数据
                await fw.writeAsync(f, stm);

                //合并文件
                if (f.complete && !await fw.writeLastPartAsync(f))
                {
                    var jv = new
                    {
                        msg = "合并文件块错误",
                        offset = -1,
                        md5 = "",
                    };
                    return new JsonResult(jv);
                }

            }
            catch (Exception e)
            {
                var jv = new
                {
                    msg = "writeBlockDataFail",
                    offset = -1,
                    md5 = "",
                    param = new
                    {
                        exception = e.Message
                    }
                };
                return new JsonResult(jv);
            }

            //触发事件
            up6_biz_event.file_post_block(f.id, f.blockIndex);
        }
        catch (Up6Exception e) {
            return e.res;
        }

        var ret = new
        {
            msg = "ok",
            offset = f.blockOffset,
            md5 = "",
            fields = new { object_id = f.object_id }
        };
        return new JsonResult(ret);
    }
    public IActionResult complete(FileInf f)
    {
        int ret = 0;
        if (!string.IsNullOrEmpty(f.id))
        {
            up6.db.database.up6.sql.SqlFile.build().complete(f.id);
            ret = 1;
        }
        return new ContentResult
        {
            Content = f.callback+"("+ret+")",
            ContentType = "application/json; charset=utf-8",
            StatusCode = 200
        };
    }
    public IActionResult del(FileInf f)
    {
        int ret = 0;
        if (!string.IsNullOrEmpty(f.id))
        {
            up6.db.database.up6.sql.SqlFile.build().Delete(f.uid,f.id);
            ret = 1;
        }
        return new ContentResult
        {
            Content = f.callback + "(" + ret + ")",
            ContentType = "application/json; charset=utf-8",
            StatusCode = 200
        };
    }
    public IActionResult list(FileInf f)
    {
        var fs = up6.db.database.up6.sql.SqlFile.build().uncmps(f.uid);
        if(!string.IsNullOrEmpty(fs))
        {
            fs = WebUtility.UrlEncode(fs);
            //UrlEncode会将空格解析成+号
            fs = fs.Replace("+", "%20");
            return new ContentResult
            {
                Content = f.callback + "({\"value\":\""+ fs+"\"})",
                ContentType = "application/json; charset=utf-8",
                StatusCode = 200
            };
        }

        return new ContentResult
        {
            Content = f.callback + "({\"value\":null})",
            ContentType = "application/json; charset=utf-8",
            StatusCode = 200
        };
    }

    public IActionResult process(FileInf f)
    {
        int ret = 0;
        if (!string.IsNullOrEmpty(f.id)&&
            !string.IsNullOrEmpty(f.perSvr))
        {
            up6.db.database.up6.sql.SqlFile.build().process(f.uid, f.id, f.offset, f.lenSvr, f.perSvr);
            ret = 1;
        }
        return new ContentResult
        {
            Content = f.callback + "({\"state\":" + ret + "})",
            ContentType = "application/json; charset=utf-8",
            StatusCode = 200
        };
    }

    /// <summary>
    /// 文件夹初始化
    /// </summary>
    /// <param name="f"></param>
    /// <returns></returns>
    public IActionResult fd_create(FileInf f)
    {
        //生成路径
        PathBuilderUuid pb = new PathBuilderUuid();
        f.pathSvr = pb.genFolder(f);
        //创建文件夹错误
        if(!PathTool.mkdir(f.pathSvr))
        {
            var js = new JObject { { "msg", "create dir error" } };

            return new ContentResult
            {
                Content = f.callback + string.Format("({0})",JsonConvert.SerializeObject(js)),
                ContentType = "application/json; charset=utf-8",
                StatusCode = 500
            };
        }

        //创建层级信息文件
        FolderSchema fs = new FolderSchema();
        if (!fs.create(f))
        {
            var js = new JObject { { "msg", "create schema file error" } };
            return new ContentResult
            {
                Content = f.callback + string.Format("({0})",JsonConvert.SerializeObject(js)),
                ContentType = "application/json; charset=utf-8",
                StatusCode = 500
            };
        }
        up6.db.database.up6.sql.SqlFile.build().Add(f);
        var json = JsonConvert.SerializeObject(f);
        json = WebUtility.UrlEncode(json);
        json = json.Replace("+", "%20");
        var jo = new JObject { { "value",json} };

        return new ContentResult
        {
            Content = f.callback + string.Format("({0})",JsonConvert.SerializeObject(jo)),
            ContentType = "application/json; charset=utf-8",
        };
    }

    public IActionResult fd_complete(FileInf f)
    {
        int ret = 0;
        if(!string.IsNullOrEmpty(f.id))
        {
            var sql = up6.db.database.up6.sql.SqlFile.build();
            var folder = sql.read(f.id);
            folder.uid= f.uid;

            //保存层级结构-解析层级信息文件
            FolderSchemaDB fsd = new FolderSchemaDB();
            fsd.save(folder);

            //上传完毕
            sql.complete(f.id);
            ret = 1;
        }

        return new ContentResult
        {
            Content = f.callback + "(" + ret + ")",
            ContentType = "application/json; charset=utf-8",
        };
    }

    public IActionResult fd_del(FileInf f)
    {
        int ret = 0;
        if (!string.IsNullOrEmpty(f.id))
        {
            up6.db.database.up6.sql.SqlFolder.build().del(f.id,f.uid);
            ret = 1;
        }

        return new ContentResult
        {
            Content = f.callback + "({\"value\":" + ret + "})",
            ContentType = "application/json; charset=utf-8",            
        };
    }

    /// <summary>
    /// 验证块md5
    /// </summary>
    /// <param name="blockMd5"></param>
    /// <param name="stm"></param>
    /// <returns></returns>
    /// <exception cref="Up6Exception"></exception>
    bool checkBlockMd5(string blockMd5,Stream stm)
    {
        if (!string.IsNullOrEmpty(blockMd5))
        {
            string md5Svr = Md5Tool.calc(stm);
            if (md5Svr != blockMd5)
            {
                var ret = new
                {
                    msg = "文件块MD5不匹配",
                    code = "blockMd5Different",
                    offset = -1,
                    md5 = "",
                    param = new { 
                        md5Svr = md5Svr,
                        md5Loc=blockMd5
                    }
                };
                throw new Up6Exception( new JsonResult(ret));
            }
        }
        return true;
    }

    /// <summary>
    /// 解压
    /// </summary>
    /// <param name="f"></param>
    /// <param name="s"></param>
    /// <returns></returns>
    /// <exception cref="Up6Exception"></exception>
    Stream unCompress(FileInf f,Stream s)
    {
        //未压缩
        if (f.blockSizeCpr == 0) return s;

        //块大小不同
        if (s.Length != f.blockSizeCpr)
        {
            var ret = new
            {
                msg = "块大小不同",
                code = "blockSizeCprDifferent",
                offset = -1,
                md5 = "",
                param = new { 
                    blockSizeRecv = s.Length ,
                    blockSizeSend=f.blockSizeCpr
                }
            };
            throw new Up6Exception(new JsonResult(ret));
        }

        return UtilsTool.unCompress(s, f.blockCprType);
    }


    /// <summary>
    /// 验证块大小，并解密块数据
    /// </summary>
    /// <param name="stm">加密块数据</param>
    /// <param name="f">块信息</param>
    /// <returns></returns>
    Stream decryptBlock(Stream stm, FileInf f)
    {
        //未加密
        if (f.blockSizeSec == 0) return stm;

        //块大小不同
        if (stm.Length != f.blockSizeSec)
        {
            var ret = new
            {
                msg = "块解密错误",
                code = "blockSizeCryDifferent",
                offset = -1,
                md5 = "",
                param = new
                {
                    blockSizeRecv = stm.Length,
                    blockSizeSend = f.blockSizeSec
                }
            };
            throw new Up6Exception(new JsonResult(ret));
        }

        //加密存储
        if (f.encrypt) return stm;

        CryptoTool ct = new CryptoTool();
        stm = ct.decode(f.encryptAgo, stm);
        return stm;
    }
    void validBlockSize(Stream s, FileInf f)
    {
        //已加密，由解密模块判断
        if (f.blockSizeSec > 0) return;

        //使用对象存储
        if (ConfigReader.storage() != StorageType.IO)
        {
            //块大小不能小于5MB
            if (f.blockCount > 1 &&
                f.blockIndex == 1 &&
                f.blockSize < 5242880)
            {
                var ret = new
                {
                    msg = "对象存储块大小小于5MB",
                    code = "blockSizeCryDifferent",
                    offset = -1,
                    md5 = ""
                };
                throw new Up6Exception(new JsonResult(ret));
            }
        }

        //未加密
        if (f.blockSize != s.Length)
        {
            var ret = new
            {
                msg = "块大小错误",
                code = "blockSizeCryDifferent",
                offset = -1,
                md5 = "",
                param = new
                {
                    blockSizeRecv = s.Length,
                    blockSizeSend = f.blockSize
                }
            };
            throw new Up6Exception(new JsonResult(ret));
        }
    }

    void checkToken(FileInf f)
    {
        WebSafe ws = new WebSafe();
        if (!ws.validToken(f.token, f, "block"))
        {
            var ret = new
            {
                msg = "token验证失败",
                code = "blockMd5Different",
                offset = -1,
                md5 = "",
                param = new
                {
                    action = "block"
                }
            };
            throw new Up6Exception(new JsonResult(ret));
        }
    }
}
